﻿using Gargoyle.Roton;
using System;
using System.Collections.Generic;
using System.Text;

namespace Gargoyle.ZZT
{
    sealed public class Sounds
    {
        public byte[] Ammo;
        public byte[] BombActivate;
        public byte[] BombExplode;
        public byte[] BombTick;
        public byte[] BombTock;
        public byte[] BulletDie;
        public byte[] Cheat;
        public byte[] DoorLocked;
        public byte[] DoorOpen;
        public byte[] Duplicate;
        public byte[] DuplicateFail;
        public byte[] EnemyDie;
        public byte[] EnemyShoot;
        public byte[] EnemySuicide;
        public byte[] Energizer;
        public byte[] EnergyOut;
        public byte[] Error;
        public byte[] Forest;
        public byte[] GameOver;
        public byte[] Gem;
        public byte[] Invisible;
        public byte[] Key;
        public byte[] KeyAlready;
        public byte[] Ouch;
        public byte[] Passage;
        public byte[] Push;
        public byte[] Ricochet;
        public byte[] Shoot;
        public byte[] SlimeDie;
        public byte[] TimeLow;
        public byte[] TimeOut;
        public byte[] Torch;
        public byte[] TorchOut;
        public byte[] Transporter;
        public byte[] Water;
    }

    public partial class Game : Roton.Environment
    {
        internal void ClearSound()
        {
            State.SoundData.Clear();
            State.SoundPlaying = false;
            StopSound();
        }

        private void EnqueueSoundData(byte[] soundData)
        {
            int index = 0;
            while (index < soundData.Length && State.SoundData.Count < Parameters.SoundDataSize)
            {
                byte note = soundData[index];
                byte duration = soundData[index + 1];
                State.SoundData.Enqueue(note);
                State.SoundData.Enqueue(duration);
                index += 2;
            }
        }

        internal void ExecuteSound()
        {
            if (State.Quiet)
            {
                State.SoundPlaying = false;
                StopSound();
                return;
            }

            if(!State.SoundPlaying)
            {
                Audio.Silence();
                return;
            }

            // needs to be unchecked for 0->255 wraparound for 0 duration
            unchecked
            {
                State.SoundTicks--;
                if (State.SoundTicks > 0)
                return;
            }

            if (State.SoundData.Count == 0)
            {
                State.SoundPlaying = false;
                State.SoundPriority = 0;
                return;
            }

            byte sound = State.SoundData.Dequeue();
            int duration = State.SoundData.Dequeue();
            State.SoundTicks = (byte)((duration * 4) & 0xFF);

            if (!State.Quiet)
            {
                if (sound >= 0xF0 && sound <= 0xF9 && sound != 0xF3)
                {
                    Audio.PlaySound(sound & 0x0F, duration);
                }
                else if(sound < 0xF0 && State.SoundTicks > 0)
                {
                    int octave = (sound & 0xF0) >> 4;
                    int note = (sound & 0x0F) + (octave * 12);
                    double freq = (double)440 * Math.Pow(2, ((double)note - 45) / 12);
                    Audio.PlayTone(freq, duration);
                }
                else
                {
                    Audio.Rest(duration);
                }
            }
        }

        internal void PlayMusic(int priority, ByteString musicData)
        {
            PlaySound(priority, ProcessMusic(musicData), false);
        }

        internal void PlaySound(int priority, ByteString soundData)
        {
            PlaySound(priority, soundData, true);
        }

        internal void PlaySound(int priority, ByteString soundData, bool adjustDuration)
        {
            if(State.GameOver)
                return;

            if(!State.SoundPlaying || priority == -1 || (State.SoundPriority != -1 && priority >= State.SoundPriority))
            {
                if(State.SoundPriority >= 0)
                {
                    if(priority >= 0)
                        State.SoundData.Clear();
                }
                State.SoundPriority = priority;
                EnqueueSoundData(soundData);
                State.SoundPlaying = true;
                State.SoundTicks = 1;
            }
        }

        internal void PlayStepSound()
        {
			if (!State.Quiet && !State.GameOver)
            {
                if (!State.SoundPlaying && State.SoundData.Count == 0 && State.SoundTicks == 0)
                {
                    Audio.PlaySound(3);
                }
            }
        }

        private byte[] ProcessMusic(ByteString musicData)
        {
            List<byte> result = new List<byte>();
            byte octave = 3;
            byte speed = 1;
            byte note = 0;
            int index = 0;

            musicData = UpperCase(musicData);

            while (index < musicData.Length)
            {
                byte dataByte = musicData[index];
                note = 0xFE;
                index++;

                switch (dataByte)
                {
                    case 0x54: // T
                        speed = 1;
                        break;
                    case 0x53: // S
                        speed = 2;
                        break;
                    case 0x49: // I
                        speed = 4;
                        break;
                    case 0x51: // Q
                        speed = 8;
                        break;
                    case 0x48: // H
                        speed = 16;
                        break;
                    case 0x57: // W
                        speed = 32;
                        break;
                    case 0x2E: // .
                        if(speed > 1) // in ZZT, t. == t
                            speed = (byte)((speed / 2) * 3);
                        break;
                    case 0x33: // 3
                        speed = (byte)(speed / 3);
                        break;
                    case 0x2B: // +
                        if (octave < 6)
                            octave++;
                        break;
                    case 0x2D: // -
                        if (octave > 1)
                            octave--;
                        break;
                    case 0x43: // C
                        note = 0x00;
                        break;
                    case 0x44: // D
                        note = 0x02;
                        break;
                    case 0x45: // E
                        note = 0x04;
                        break;
                    case 0x46: // F
                        note = 0x05;
                        break;
                    case 0x47: // G
                        note = 0x07;
                        break;
                    case 0x41: // A
                        note = 0x09;
                        break;
                    case 0x42: // B
                        note = 0x0B;
                        break;
                    case 0x30: // 0
                        note = 0xF0;
                        break;
                    case 0x31: // 1
                        note = 0xF1;
                        break;
                    case 0x32: // 2
                        note = 0xF2;
                        break;
                    case 0x34: // 4
                        note = 0xF4;
                        break;
                    case 0x35: // 5
                        note = 0xF5;
                        break;
                    case 0x36: // 6
                        note = 0xF6;
                        break;
                    case 0x37: // 7
                        note = 0xF7;
                        break;
                    case 0x38: // 8
                        note = 0xF8;
                        break;
                    case 0x39: // 9
                        note = 0xF9;
                        break;
                    case 0x58: // X
                        note = 0xFF;
                        break;
                }
                if (note != 0xFE)
                {
                    if (note < 0xF0)
                    {
                        note = (byte)(note | (octave << 4));
                    }

                    if (index < musicData.Length)
                    {
                        byte accidental = musicData[index];
                        switch (accidental)
                        {
                            case 0x21: // !
                                note--;
                                break;
                            case 0x23: // #
                                note++;
                                break;
                        }
                    }
                    result.Add(note);
                    result.Add(speed);
                }
            }

            return result.ToArray();
        }

        internal void StopSound()
        {
            Audio.Stop();
            State.SoundData.Clear();
            State.SoundPlaying = false;
            State.SoundTicks = 0;
        }
    }
}
